package dk.eclipse.android.badassapp.distance;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.List;
import java.util.Stack;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import android.util.Log;

/**
 * @author jaw
 * 
 *         Java implementation of GeoCoder. THis is written because GeoCoder
 *         does not work in the emulator. Only geocoding and reverse geocoding
 *         is implemented.
 */
public class HttpGeoCoder {

	private static final String TAG = HttpGeoCoder.class.getName();

	class SAXStopException extends SAXException {

		public SAXStopException(String string) {
			super(string);
		}

	}

	public static interface AddressFoundCallback {
		void addressFound(int i);
	}

	public static class MyAddress {

		public String mFormattedAddress = "";
		public String mLongitude = "";
		public String mLatitude = "";

		public void setFormattedAddress(String string) {
			mFormattedAddress = string;
		}

		public String getFormattedAddress() {
			return mFormattedAddress;
		}

		public String getLongitude() {
			return mLongitude;
		}

		public void setLongitude(String string) {
			mLongitude = string;
		}

		public String getLatitude() {
			return mLatitude;
		}

		public void setLatitude(String string) {
			mLatitude = string;
		}

	}

	interface ICanHandleChars {
		void characters(char[] ch, int start, int length);
	}

	private class MySaxHandler implements ContentHandler {

		private final String TAG = MySaxHandler.class.getName();
		Stack<ICanHandleChars> waitingForCharacters = new Stack<ICanHandleChars>();

		@Override
		public void characters(char[] ch, int start, int length)
				throws SAXException {
			try {
				waitingForCharacters.peek().characters(ch, start, length);
			} catch (EmptyStackException e) {
				// OK that characters we are not waiting arrives
			}
		}

		@Override
		public void endElement(String uri, String localName, String qName)
				throws SAXException {
			try {
				if ("result".equals(localName)) {
					// Nofify caller
					mAddressFoundCallback.addressFound(mAddresss.size());
					if (mAddresss.size() >= mStopAfter) {
						throw new SAXStopException(
								"Reached required number of addresses. Stopping.");
					}
				}

				waitingForCharacters.pop();
			} catch (EmptyStackException e) {
				// OK because it is only legal to wait for characters in
				// yourself or subelements of yourself.
			}
		}

		@Override
		public void startElement(String uri, String localName, String qName,
				Attributes atts) throws SAXException {
			// TODO Auto-generated method stub
			Log.i(TAG, localName + " " + qName);
			if ("result".equals(localName)) {
				MyAddress a = new MyAddress();
				mAddresss.push(a);
			} else if ("formatted_address".equals(localName)) {
				waitingForCharacters.push(new ICanHandleChars() {
					@Override
					public void characters(char[] ch, int start, int length) {
						MyAddress peek = mAddresss.peek();
						// characters method of SAX parser may chunk up
						// characters
						peek.setFormattedAddress(peek.getFormattedAddress()
								+ new String(ch, start, length));
					}
				});
			} else if ("location".equals(localName)) {
				// latitude, longitude follows location.
				// Register 2 handlers to pick up the values.
				waitingForCharacters.push(new ICanHandleChars() {

					@Override
					public void characters(char[] ch, int start, int length) {
						MyAddress peek = mAddresss.peek();
						peek.setLongitude(peek.getLongitude()
								+ new String(ch, start, length));
					}
				});
				waitingForCharacters.push(new ICanHandleChars() {

					@Override
					public void characters(char[] ch, int start, int length) {
						MyAddress peek = mAddresss.peek();
						peek.setLatitude(peek.getLatitude()
								+ new String(ch, start, length));
					}
				});
			}
		}

		@Override
		public void startPrefixMapping(String prefix, String uri)
				throws SAXException {
		}

		@Override
		public void endDocument() throws SAXException {
		}

		@Override
		public void endPrefixMapping(String prefix) throws SAXException {
		}

		@Override
		public void ignorableWhitespace(char[] ch, int start, int length)
				throws SAXException {
		}

		@Override
		public void processingInstruction(String target, String data)
				throws SAXException {
		}

		@Override
		public void setDocumentLocator(Locator locator) {
		}

		@Override
		public void skippedEntity(String name) throws SAXException {
		}

		@Override
		public void startDocument() throws SAXException {
		}

	}

	private MySaxHandler mySaxHandler = new MySaxHandler();

	private Stack<MyAddress> mAddresss;

	private AddressFoundCallback dummyCb = new AddressFoundCallback() {

		/**
		 * @param i
		 *            address number found
		 */
		@Override
		public void addressFound(int i) {
			// Dummy - do nothing
		}
	};

	private AddressFoundCallback mAddressFoundCallback = dummyCb;
	private int mStopAfter;

	public HttpGeoCoder() {
	}

	public List<MyAddress> geocode(double latitude, double longitude)
			throws IOException, SAXException, ParserConfigurationException {
		return geocode(latitude, longitude, dummyCb, -1);
	}

	/**
	 * @param latitude
	 * @param longitude
	 * @param addressFoundCallback
	 * @param stopAfter
	 *            stop after "stopafter" addresses. If stopaftere < 1, parse all
	 *            addresses
	 * @return list of addresses parsed
	 * @throws IOException
	 * @throws SAXException
	 * @throws ParserConfigurationException
	 */
	public List<MyAddress> geocode(double latitude, double longitude,
			AddressFoundCallback addressFoundCallback, int stopAfter)
			throws IOException, SAXException, ParserConfigurationException {
		mAddresss = new Stack<MyAddress>();
		mAddressFoundCallback = addressFoundCallback;
		stopAfter = stopAfter < 1 ? Integer.MAX_VALUE : stopAfter;

		URL url = new URL(
				"http://maps.googleapis.com/maps/api/geocode/xml?latlng="
						+ latitude + "," + longitude + "&sensor=false");

		InputStream is = url.openConnection().getInputStream();
		XMLReader xr = SAXParserFactory.newInstance().newSAXParser()
				.getXMLReader();
		xr.setContentHandler(mySaxHandler);
		try {
			xr.parse(new InputSource(is));
		} catch (SAXStopException e) {
			Log.i(TAG,
					"Reached requested number of addresses. Stopping parsing.");
		}
		return new ArrayList<MyAddress>(mAddresss);

	}

	public List<MyAddress> geoCode(String address) throws IOException,
			SAXException, ParserConfigurationException {
		return geoCode(address, dummyCb, -1);
	}

	/**
	 * @param address
	 *            human readable address
	 * @param addressFoundCallback
	 *            called for each address found
	 * @param stopAfter
	 *            stop parsing after n addresses. If stopAfter is <1 all
	 *            addresses will be parsed
	 * @return
	 * @throws IOException
	 * @throws SAXException
	 * @throws ParserConfigurationException
	 */
	public List<MyAddress> geoCode(String address,
			AddressFoundCallback addressFoundCallback, int stopAfter)
			throws IOException, SAXException, ParserConfigurationException {
		mAddresss = new Stack<MyAddress>();
		mAddressFoundCallback = addressFoundCallback;
		mStopAfter = stopAfter < 1 ? Integer.MAX_VALUE : stopAfter;

		URL url = new URL(
				"http://maps.googleapis.com/maps/api/geocode/xml?sensor=false&address='"
						+ address + "'");
		InputStream is = url.openConnection().getInputStream();
		XMLReader xr = SAXParserFactory.newInstance().newSAXParser()
				.getXMLReader();
		xr.setContentHandler(mySaxHandler);
		try {
			xr.parse(new InputSource(is));
		} catch (SAXStopException e) {
			Log.i(TAG,
					"Reached requested number of addresses. Stopping parsing.");
		}
		return new ArrayList<MyAddress>(mAddresss);
	}
}
